package com.varian.service.exception;

import cn.hutool.core.map.MapBuilder;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.StrUtil;
import com.varian.log.util.LogUtil;
import com.varian.service.exception.handler.ExceptionHandlerStrategy;
import com.varian.tool.Func;
import com.varian.tool.contant.VarianConstant;
import com.varian.tool.exception.BizException;
import com.varian.tool.result.IR;
import com.varian.tool.result.R;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authorization.AuthorizationDeniedException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;

/**
 * @author ben
 * @since 2024/6/27
 */
@Slf4j
@RestControllerAdvice
public class GlobalWebExceptionHandler {

    private final Map<Class<?>, ExceptionHandlerStrategy> strategyMap;
    private final Set<Class<? extends Exception>> ignoreExceptionSet;
    private final BizExceptionResolve bizExceptionResolve;

    public GlobalWebExceptionHandler(BizExceptionResolve bizExceptionResolve) {
        this.bizExceptionResolve = bizExceptionResolve;
        MapBuilder<Class<?>, ExceptionHandlerStrategy> builder = MapUtil.builder();
        Consumer<Object> addHandler = o -> builder.put(((ExceptionHandlerStrategy) o).getExceptionClass(), (ExceptionHandlerStrategy) o);
        List<ExceptionHandlerStrategy> handlers = Func.newInstanceBySuper(VarianConstant.ROOT_PACKAGE_NAME, ExceptionHandlerStrategy.class);
        handlers.forEach(addHandler);
        this.strategyMap = builder.build();
        this.ignoreExceptionSet = Set.of(
                AuthorizationDeniedException.class
        );
        log.info("init exception handler: {}", strategyMap.size());
    }

    @ExceptionHandler(BizException.class)
    public IR bizExceptionHandler(BizException exception, HttpServletResponse response) {
        log.error("biz exception: {}", exception.getMessage(), exception);
        String message = exception.getMessage();
        String bizExceptionMessage = bizExceptionResolve.getBizExceptionMessage(exception.getCode());
        if (StrUtil.isNotEmpty(bizExceptionMessage)) {
            message = StrUtil.format(bizExceptionMessage, exception.getParams());
        }
        response.setStatus(exception.getStatus());
        return R.fail(exception.getCode(), message);
    }

    @SneakyThrows
    @ExceptionHandler(Exception.class)
    public IR handle(Exception exception, HttpServletRequest request, HttpServletResponse response) {
        log.error("handler exception: {}", exception.getClass(), exception);
        if (ignoreExceptionSet.contains(exception.getClass())) {
            throw exception;
        }
        IR result;
        if (strategyMap.containsKey(exception.getClass())) {
            ExceptionHandlerStrategy strategy = strategyMap.get(exception.getClass());
            result = strategy.handle(exception, response);
        } else {
            result = R.serverError();
        }
        response.setStatus(result.getStatus());
        LogUtil.apiErrorLog(request, exception, result);
        return result;
    }
}
